//! High level container types for modules.

use stencil_tysys::UserTypeId;
use stencil_util::{FieldName, TypeName};

use super::{field::FieldLayoutEntry, NativeTypeTd, StructBufTd, Typedef};

#[derive(Clone, Debug)]
pub struct ModuleDef {
    ty_entries: Vec<ModTypedefEntry>,
}

impl ModuleDef {
    pub fn new(ty_entries: Vec<ModTypedefEntry>) -> Self {
        Self { ty_entries }
    }

    pub fn typedefs(&self) -> &[ModTypedefEntry] {
        &self.ty_entries
    }

    fn find_typedef_by(&self, f: impl Fn(&ModTypedefEntry) -> bool) -> Option<&ModTypedefEntry> {
        self.ty_entries.iter().filter(|e| f(e)).next()
    }

    pub fn find_typedef_by_name(&self, name: &TypeName) -> Option<&ModTypedefEntry> {
        self.find_typedef_by(|d| d.name() == name)
    }

    pub fn find_typedef_by_id(&self, id: UserTypeId) -> Option<&ModTypedefEntry> {
        self.find_typedef_by(|d| d.user_type_id() == id)
    }
}

/// An entry for a typedef within a module.
///
/// This also includes a type ID that we can start to solve for.
#[derive(Clone, Debug)]
pub struct ModTypedefEntry {
    user_type_id: UserTypeId,
    def: ModTypedef,
}

impl ModTypedefEntry {
    pub fn new(user_type_id: UserTypeId, def: ModTypedef) -> Self {
        Self { user_type_id, def }
    }

    pub fn user_type_id(&self) -> UserTypeId {
        self.user_type_id
    }

    pub fn def(&self) -> &ModTypedef {
        &self.def
    }

    /// Gets the name of the type.
    pub fn name(&self) -> &TypeName {
        self.def().name()
    }
}

/// Typedef within in module.
#[derive(Clone, Debug)]
pub enum ModTypedef {
    /// Native types used for utility purposes.
    Native(NativeTypeTd),

    /// Struct buffer types.
    StructBuf(StructBufTd),
}

impl ModTypedef {
    pub fn name(&self) -> &TypeName {
        match self {
            ModTypedef::Native(td) => td.name(),
            ModTypedef::StructBuf(td) => td.name(),
        }
    }
}

pub struct BufContainerEntry {
    name: TypeName,
    layout: BufContainerLayout,
}

impl BufContainerEntry {
    pub fn new(name: TypeName, layout: BufContainerLayout) -> Self {
        Self { name, layout }
    }

    pub fn name(&self) -> &TypeName {
        &self.name
    }

    pub fn layout(&self) -> &BufContainerLayout {
        &self.layout
    }
}

pub struct BufContainerLayout {
    fields: Vec<FieldLayoutEntry>,
}

impl BufContainerLayout {
    pub fn new(fields: Vec<FieldLayoutEntry>) -> Self {
        Self { fields }
    }

    pub fn fields(&self) -> &[FieldLayoutEntry] {
        &self.fields
    }

    pub fn find_field_entry(&self, name: &FieldName) -> Option<&FieldLayoutEntry> {
        self.fields.iter().find(|e| e.name() == name)
    }
}
